package com.hrg.rds.infra.connect.protocol.s7;

import cn.hutool.core.util.HexUtil;
import lombok.Data;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.messages.PlcReadResponse;
import org.apache.plc4x.java.api.messages.PlcWriteRequest;
import org.apache.plc4x.java.api.messages.PlcWriteResponse;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.api.value.PlcValue;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
// 通用地址格式，array-size省略时默认是1
// %{Memory-Area}{start-address}:{Data-Type}[{array-size}]
// "%Q0.4:BOOL"
// "%Q0:BYTE"
// "%I0.2:BOOL"
// "%DB.DB1.4:INT"

//两种类型地址：Bit addresses 只为 BOOL 使用.
// Bit-Addresses // {Memory-Area-Code}{Start-Byte-Address}.{Bit-Offset}:BOOL[{Count}]
// Byte-Addresses // {Memory-Area-Code}{Start-Byte-Address}:{Data-Type-Code}[{count}]

// 数据块的语法复杂点
// DB{Data-Block-Number}.DB{Short-Data-Type-Code}{Start-Byte-Address}.{Bit-Offset}:BOOL[{Count}]
// DB{500}.DB{X}2.1

// DB{Data-Block-Number}.DB{Short-Data-Type-Code}{Start-Byte-Address}:{Data-Type-Code}[{Count}]
// 当datatype 为 STRING时，默认返回254个字符，可通过指定string-length来读取指定长度的字符串
// DB{Data-Block-Number}.DB{Short-Data-Type-Code}{Start-Byte-Address}:STRING({string-length})[{Count}]

// 简化语法
// DB{Data-Block-Number}:{Start-Byte-Address}.{Bit-Offset}:BOOL[{Count}]
// DB{Data-Block-Number}:{Start-Byte-Address}:{Data-Type-Code}[{Count}]
// DB{Data-Block-Number}:{Start-Byte-Address}:STRING({string-length})[{Count}]


/*标签名称的唯一性：在一个请求里，每个标签的名称都必须是唯一的，这样才能在处理响应的时候准确获取对应标签的值。
地址格式的正确性：不同的工业协议有不同的标签地址格式，在使用 addTagAddress 方法时，要保证提供的地址格式是正确的，不然可能会导致连接或者读写失败。
数据类型的匹配性：在写入数据时，要保证提供的数据类型和标签地址所对应的数据类型相匹配，否则也会出现写入失败的情况。*/

// read/write or a subscription
public class S7Util {
	private final static String TagPrefix = "value-";
	private final static int Timeout = 2;

	public static List<String> readBatch(PlcConnection connection, List<S7SingleReq> s7SingleReqList) throws Exception {
		List<String> result = new ArrayList<>();

		PlcReadRequest.Builder builder = connection.readRequestBuilder();
		for (int i = 0; i < s7SingleReqList.size(); i++) {
			S7SingleReq s7SingleReq = s7SingleReqList.get(i);
			String tagAddress;

			S7MemoryArea s7MemoryArea = s7SingleReq.getS7MemoryArea();
			S7DataType s7DataType = s7SingleReq.getS7DataType();
			int startAddress = s7SingleReq.getStartByteAddress();
			if (s7DataType.equals(S7DataType.BOOL)) {
				if (s7MemoryArea.equals(S7MemoryArea.DB)) {
					tagAddress = String.format("%%DB%d:%d.%d:BOOL", s7SingleReq.getDataBlockNumber(), startAddress, s7SingleReq.getBitOffset());
				} else {
					tagAddress = String.format("%%%s%d.%d:BOOL", s7MemoryArea.name(), startAddress, s7SingleReq.getBitOffset());
				}
			} else {
				if (s7MemoryArea.equals(S7MemoryArea.DB)) {
					if (s7DataType.equals(S7DataType.STRING)) {
						tagAddress = String.format("%%DB%d:%d:STRING(%d)", s7SingleReq.getDataBlockNumber(), startAddress, s7SingleReq.getStringLength());
					} else {
						tagAddress = String.format("%%DB%d:%d:%s", s7SingleReq.getDataBlockNumber(), startAddress, s7DataType.name());
					}
				} else {
					tagAddress = String.format("%%%s%d:%s", s7MemoryArea.name(), startAddress, s7DataType.name());
				}
			}
			String tagName = TagPrefix+i;
			builder.addTagAddress(tagName, tagAddress);
		}

		//
		PlcReadRequest readRequest = builder.build();
		//同步
		PlcReadResponse response = readRequest.execute().get(Timeout, TimeUnit.SECONDS);
		for (int i = 0; i < s7SingleReqList.size(); i++) {
			String tagName = TagPrefix+i;
			if (response.getResponseCode(tagName) == PlcResponseCode.OK) {
				PlcValue plcValue = response.getPlcValue(tagName);
				if (plcValue.isNull()) {
					result.add(null);
				} else {
					result.add(HexUtil.encodeHexStr(plcValue.getRaw()));
				}
			}
		}
		return result;
	}

	public static String readSingle(PlcConnection connection, S7SingleReq s7SingleReq) throws Exception {
		String result = null;

		String tagAddress;
		S7MemoryArea s7MemoryArea = s7SingleReq.getS7MemoryArea();
		S7DataType s7DataType = s7SingleReq.getS7DataType();
		int startAddress = s7SingleReq.getStartByteAddress();
		if (s7DataType.equals(S7DataType.BOOL)) {
			if (s7MemoryArea.equals(S7MemoryArea.DB)) {
				tagAddress = String.format("%%DB%d:%d.%d:BOOL", s7SingleReq.getDataBlockNumber(), startAddress, s7SingleReq.getBitOffset());
			} else {
				tagAddress = String.format("%%%s%d.%d:BOOL", s7MemoryArea.name(), startAddress, s7SingleReq.getBitOffset());
			}
		} else {
			if (s7MemoryArea.equals(S7MemoryArea.DB)) {
				if (s7DataType.equals(S7DataType.STRING)) {
					tagAddress = String.format("%%DB%d:%d:STRING(%d)", s7SingleReq.getDataBlockNumber(), startAddress, s7SingleReq.getStringLength());
				} else {
					tagAddress = String.format("%%DB%d:%d:%s", s7SingleReq.getDataBlockNumber(), startAddress, s7DataType.name());
				}
			} else {
				tagAddress = String.format("%%%s%d:%s", s7MemoryArea.name(), startAddress, s7DataType.name());
			}
		}
		//
		PlcReadRequest.Builder builder = connection.readRequestBuilder();
		String tagName = TagPrefix+0;
		builder.addTagAddress(tagName, tagAddress);
		PlcReadRequest readRequest = builder.build();
		//同步
		PlcReadResponse response = readRequest.execute().get(Timeout, TimeUnit.SECONDS);
		if (response.getResponseCode(tagName) == PlcResponseCode.OK) {
			PlcValue plcValue = response.getPlcValue(tagName);
			if (!plcValue.isNull()) {
				result = HexUtil.encodeHexStr(plcValue.getRaw());
			}
		}
		return result;
	}

	public static boolean writeSingle(PlcConnection connection, S7SingleReq s7SingleReq) throws Exception {
		boolean result = false;

		String tagAddress;
		S7MemoryArea s7MemoryArea = s7SingleReq.getS7MemoryArea();
		S7DataType s7DataType = s7SingleReq.getS7DataType();
		int startAddress = s7SingleReq.getStartByteAddress();
		if (s7DataType.equals(S7DataType.BOOL)) {
			if (s7MemoryArea.equals(S7MemoryArea.DB)) {
				tagAddress = String.format("%%DB%d:%d.%d:BOOL", s7SingleReq.getDataBlockNumber(), startAddress, s7SingleReq.getBitOffset());
			} else {
				tagAddress = String.format("%%%s%d.%d:BOOL", s7MemoryArea.name(), startAddress, s7SingleReq.getBitOffset());
			}
		} else {
			if (s7MemoryArea.equals(S7MemoryArea.DB)) {
				if (s7DataType.equals(S7DataType.STRING)) {
					tagAddress = String.format("%%DB%d:%d:STRING(%d)", s7SingleReq.getDataBlockNumber(), startAddress, s7SingleReq.getStringLength());
				} else {
					tagAddress = String.format("%%DB%d:%d:%s", s7SingleReq.getDataBlockNumber(), startAddress, s7DataType.name());
				}
			} else {
				tagAddress = String.format("%%%s%d:%s", s7MemoryArea.name(), startAddress, s7DataType.name());
			}
		}
		//
		PlcWriteRequest.Builder builder = connection.writeRequestBuilder();
		String tagName = TagPrefix+0;
		builder.addTagAddress(tagName, tagAddress, s7SingleReq.getValue());
		PlcWriteRequest writeRequest = builder.build();
		//同步
		PlcWriteResponse response = writeRequest.execute().get(Timeout, TimeUnit.SECONDS);
		if (response.getResponseCode(tagName) == PlcResponseCode.OK) {
			result = true;
		}
		return result;
	}
}

